/*
 * daemon.c
 *
 *  Created on: 17/feb/2011
 *      Author: ivano
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

#include "daemon.h"
#include "list.h"

#define MAX_JOB_ID 65535
#define MIN_JOB_ID 1

static list* terminali;
static list* jobs;
static list* modules;
static list* variables;
static next_job_id = MIN_JOB_ID;

static terminal_t* launcher_terminal;

void sanitize_input(char* cmd, int length);

void daemon_forward_message (char* cmd, int but) {
	int i, size;
	terminal_t* term;

	size = list_size(terminali);
	for (i = 0; i < size; i++) {
		if (i == but)
			continue;
		term = (terminal_t*) list_get(terminali, i);
		terminal_write_to_terminal(term, cmd);
	}
}

int daemon_parse_command(char* cmd, terminal_t* term) {
	static char buffer[4096];
	static char tmp[255];
	void* ptr;
	int res, i;
	terminal_t* newterm;
	module_t* mod;
	variable_t* var;
	job_t* job;
	int (*module_init)(terminal_t* terminal);

	fprintf(stderr, "DEBUG: daemon: received string \"%s\"\n", cmd);

	if (!strncmp(cmd, "modules ", 8)) {
		cmd += 8;
		if (!strncmp(cmd, "load ", 5)) {
			cmd += 5;
			fprintf(stderr, "INFO: daemon: loading module %s\n", cmd);
			// Caricare il modulo specificato
			ptr = dlopen(cmd, RTLD_GLOBAL | RTLD_NOW);
			if (ptr == NULL) {
				fprintf(stderr, "ERROR: daemon: unable to load %s shared library. maybe this message is to a module, forwarding...\n", cmd);
				fprintf(stderr, "ERROR: daemon: dlopen: %s\n", dlerror());
				terminal_write_to_terminal(term, " * Unable to find module. Perhaps a module will do.");
				return 0;
			}
			module_init = dlsym(ptr, "module_init");
			if (module_init == NULL) {
				fprintf(stderr, "ERROR: daemon: unable to find module_init function in %s module\n", cmd);
				terminal_write_to_terminal(term, " * Unable to find module_init function in loaded module.");
				terminal_write_to_terminal(term, " * Unloading module.");
				dlclose(ptr);
				return 0;
			}
			newterm = terminal_new();
			res = module_init(newterm);
			if (res) {
				fprintf(stderr, "ERROR: daemon: module %s returned initialization with error code %d. Unloading module...\n", cmd, res);
				terminal_free(newterm);
				dlclose(ptr);
				return 0;
			}
			else
				fprintf(stderr, "INFO: daemon: module %s loaded and initialized\n", cmd);
			list_add(terminali, newterm, -1);
			// Aggiungere il modulo caricato alla lista
			list_add(modules, module_new(cmd, newterm, ptr), 0);
		}
		else if (!strcmp(cmd, "list")) {
			res = list_size(modules);
			for (i = 0; i < res; i++) {
				mod = list_get(modules, i);
				//write(term->pins.daemon_write, mod->name, strlen(mod->name));
				terminal_write_to_terminal(term, mod->name);
			}
			return 0;
		}
		return 0;
	}
	else if (!strncmp(cmd, "jobs ", 5)) {
		cmd += 5;
		if (!strcmp(cmd, "list")) {
			res = list_size(jobs);
			for (i = 0; i < res; i++) {
				job = list_get(jobs, i);
				if (!job->running)
					sprintf(tmp, "%d", job->exit_status);
				sprintf(buffer, " * %5d %10s [%15s] %s", job->id, (job->running ? "running" : "finished"), (job->running ? (job->current_status ? job->current_status : "") : tmp), job->file);
				terminal_write_to_terminal(term, buffer);
			}
			return 0;
		}
		else if (!strncmp(cmd, "new ", 4)) {
			cmd += 4;
			job = (job_t*) malloc(sizeof(job_t));
			job->running = 1;
			job->exit_status = -1;
			job->id = next_job_id;
			next_job_id = (next_job_id - MIN_JOB_ID + 1) % (MAX_JOB_ID - MIN_JOB_ID) + MIN_JOB_ID;
			job->file = (char*) malloc (strlen(cmd) + 1);
			strcpy(job->file, cmd);
			job->current_status = NULL;
			list_add(jobs, job, job->id);
			sprintf(buffer, "launch %d %s", job->id, cmd);
			terminal_write_to_terminal(launcher_terminal, buffer);
		}
		else if (!strncmp(cmd, "delete ", 7)) {
			cmd += 7;
			res = sscanf(cmd, "%d", &i);
			if (res != 1)
				return 0;
			job = (job_t*) list_remove_key(jobs, i);
			if (job == NULL)
				return 0;
			if (job->current_status)
				free(job->current_status);
			free(job->file);
			free(job);
			return 0;
		}
		else if (sscanf(cmd, "%d", &i) == 1) {
			sscanf(cmd, "%s", buffer);
			cmd += strlen(buffer);
			if (*cmd != ' ')
				return 0;
			cmd++;
			// Here i = job_id in command
			if (!strncmp(cmd, "finished ", 9)) {
				cmd += 9;
				job = list_get_key(jobs, i);
				if (job == NULL)
					return 0;
				sscanf(cmd, "%d", &job->exit_status);
				job->running = 0;
			}
			if (!strncmp(cmd, "status ", 7)) {
				cmd += 7;
				job = list_get_key(jobs, i);
				if (job == NULL)
					return 0;
				if (job->current_status)
					free(job->current_status);
				job->current_status = (char*) malloc(strlen(cmd) + 1);
				strcpy(job->current_status, cmd);
			}
		}
		return 1;
	}
	else if (!strncmp(cmd, "variables ", 10)) {
		cmd += 10;
		if (!strcmp(cmd, "list")) {
			fprintf(stderr, "INFO: daemon: retrieving variables list\n");
			i = list_size(variables);
			for (; i > 0; i--) {
				var = list_get(variables, i-1);
				strcpy(buffer, var->name);
				strcat(buffer, " = ");
				strcat(buffer, var->value);
				strcat(buffer, "\n");
				terminal_write_to_terminal(term, buffer);
			}
			return 0;
		}
		else if (!strncmp(cmd, "get ", 4)) {
			cmd += 4;
			fprintf(stderr, "INFO: daemon: getting variable value for %s\n", cmd);
			i = list_size(variables);
			for (; i > 0; i--) {
				var = list_get(variables, i-1);
				if (!strcmp(cmd, var->name)) {
					fprintf(stderr, "DEBUG: daemon: found %s = %s\n", var->name, var->value);
					terminal_write_to_terminal(term, var->value);
					return 0;
				}
			}
			fprintf(stderr, "WARNING: daemon: variable %s not found.\n", cmd);
			return 0;
		}
		else if (!strncmp(cmd, "set ", 4)) {
			cmd += 4;
			ptr = strchr(cmd, ' ');
			if (ptr == NULL)
				ptr = cmd + strlen(cmd);
			strncpy(buffer, cmd, ((char*) ptr) - cmd);
			buffer[((char*) ptr) - cmd] = 0;
			cmd = ((char*) ptr);
			if (*cmd == 0) {
				i = list_size(variables);
				for (; i > 0; i--) {
					var = list_get(variables, i-1);
					if (var == NULL)
						break;
					if (!strcmp(buffer, var->name)) {
						fprintf(stderr, "DEBUG: daemon: deleting variable %s\n", var->name);
						sprintf(tmp, "variable %s", var->name);
						terminal_write_to_terminal(launcher_terminal, tmp);
						fprintf(stderr, "DEBUG: daemon: now deleting\n");
						if (var->name) {
							fprintf(stderr, "DEBUG: daemon: freeing var->name: \"%s\"\n", var->name);
							free(var->name);
						}
						if (var->value) {
							fprintf(stderr, "DEBUG: daemon: freeing var->value: \"%s\"\n", var->value);
							free(var->value);
						}
						fprintf(stderr, "DEBUG: daemon: removing from list\n");
						list_remove_element(variables, var);
						fprintf(stderr, "DEBUG: daemon: freeing var\n");
						free(var);
						fprintf(stderr, "DEBUG: daemon: deleted\n");
						break;
					}
				}
				return 0;
			}
			cmd++;
			i = list_size(variables);
			for (; i > 0; i--) {
				var = list_get(variables, i-1);
				if (!strcmp(buffer, var->name)) {
					fprintf(stderr, "DEBUG: daemon: changing value for variable %s\n", var->name);
					free (var->value);
					var->value = (char*) malloc (sizeof(char)*(strlen(cmd) + 1));
					strcpy(var->value, cmd);
					sprintf(tmp, "variable %s %s", var->name, var->value);
					terminal_write_to_terminal(launcher_terminal, tmp);
					return 0;
				}
			}
			fprintf(stderr, "DEBUG: daemon: creating new variable %s = %s\n", buffer, cmd);
			var = (variable_t*) malloc(sizeof(variable_t));
			var->name = (char*) malloc(sizeof(char)*(strlen(buffer)));
			var->value = (char*) malloc(sizeof(char)*(strlen(cmd) + 1));
			strcpy(var->name, buffer);
			strcpy(var->value, cmd);
			list_add(variables, var, -1);
			sprintf(tmp, "variable %s %s", var->name, var->value);
			terminal_write_to_terminal(launcher_terminal, tmp);
			return 0;
		}
	}
	else if (!strncmp(cmd, "help", 5)) {
		terminal_write_to_terminal(term, "shutdown");
		terminal_write_to_terminal(term, "variables list");
		terminal_write_to_terminal(term, "variables set NAME VALUE");
		terminal_write_to_terminal(term, "variables get NAME");
		terminal_write_to_terminal(term, "modules list");
		terminal_write_to_terminal(term, "modules load MODULE_NAME");
		terminal_write_to_terminal(term, "jobs list");
		terminal_write_to_terminal(term, "jobs new FILE_NAME");
		terminal_write_to_terminal(term, "jobs delete JOB_ID");
	}
	else if (!strncmp(cmd, "shutdown", 9)) {
		fprintf(stderr, "INFO: Daemon: quitting...\n");
		exit(EXIT_SUCCESS);
	}
	return 1;
}

int daemon_task() {
	fd_set fds;
	int i, j, size, max;
	terminal_t* term;
	char buff[4097];
	char* cmd;

	buff[4096] = 0;

	while (1) {
		FD_ZERO(&fds);

		max = STDIN_FILENO;
		FD_SET(STDIN_FILENO, &fds);
		size = list_size(terminali);
		for (i = 0; i < size; i++) {
			term = (terminal_t*) list_get(terminali, i);
			FD_SET(term->pins.daemon_read, &fds);
			if (term->pins.daemon_read > max)
				max = term->pins.daemon_read;
		}

		fprintf(stderr, "INFO: daemon: waiting for commands on bus\n");

		max = select(max + 1, &fds, NULL, NULL, NULL);
		if (max == -1) {
			fprintf(stderr, "BUG: daemon: select returned -1\n");
			continue;
		}

		if (FD_ISSET(STDIN_FILENO, &fds)) {
			do {
				cmd = terminal_read_from_terminal(launcher_terminal);
				if (daemon_parse_command(cmd, NULL))
					daemon_forward_message(cmd, -1);
				free(cmd);
			} while (terminal_has_more_for_daemon(launcher_terminal));
		}
		size = list_size(terminali);
		for (i = 0; i < size; i++) {
			term = (terminal_t*) list_get(terminali, i);
			if (!FD_ISSET(term->pins.daemon_read, &fds))
				continue;
			do {
				cmd = terminal_read_from_terminal(term);
				if (daemon_parse_command(cmd, term))
					daemon_forward_message(cmd, i);
				free(cmd);
			} while (terminal_has_more_for_daemon(launcher_terminal));
		}
	}
	return EXIT_SUCCESS;
}

int prepare_daemon(terminal_t* launcher_daemon_terminal) {
	int s_id;

	umask(0);

	s_id = setsid();
	if (s_id < 0) {
		fprintf(stderr, "ERROR: daemon: unable to get SID\n");
		return EXIT_FAILURE;
	}
	fprintf(stderr, "INFO: daemon: got sid %d\n", s_id);

	// Changing Working Directory
	/*if (chdir("/") < 0) {
		fprintf(stderr, "ERROR: daemon: unable to chdir to /\n");
		return EXIT_FAILURE;
	}*/

	fprintf(stderr, "INFO: daemon: preparing daemon\n");

	// Redirecting/Closing out standard file descriptors
	if (dup2(launcher_daemon_terminal->pins.daemon_read, STDIN_FILENO) < 0) {
		fprintf(stderr, "ERROR: daemon: unable to redirect STDIN -> daemon commands pipe\n");
		return EXIT_FAILURE;
	}
	if (dup2(launcher_daemon_terminal->pins.daemon_write, STDOUT_FILENO) < 0) {
		fprintf(stderr, "ERROR: daemon: unable to redirect STDOUT -> launcher commands pipe\n");
		return EXIT_FAILURE;
	}
	close(launcher_daemon_terminal->pins.terminal_read);
	close(launcher_daemon_terminal->pins.terminal_write);

	// Preparing lists
	terminali = list_new();
	jobs = list_new();
	modules = list_new();
	variables = list_new();

	launcher_terminal = launcher_daemon_terminal;

	return EXIT_SUCCESS;
}

void sanitize_input(char* cmd, int length) {

	cmd[length--] = 0;

	for (; length >= 0; length--) {
		if (cmd[length] == '\r')
			cmd[length] = 0;
		else if (cmd[length] == '\n')
			cmd[length] = 0;
		else if (cmd[length] == '\t')
			cmd[length] = 0;
		else if (cmd[length] == ' ')
			cmd[length] = 0;
		else
			return;
	}
}

module_t* module_new(char* name, terminal_t* terminal, void* handler) {
	module_t* mod = (module_t*) malloc(sizeof(module_t));
	mod->name = (char*) malloc(strlen(name) + 1);
	strncpy(mod->name, name, strlen(name) + 1);
	mod->terminal = terminal;
	mod->handler = handler;
	return mod;
}
